﻿using System;
using System.Collections.Generic;
using System.Reflection;

using Autofac.Builder;
using Autofac.Core;

namespace Moq.Contrib.Indy
{
	/// <summary> 
	/// Resolves unknown interfaces and Mocks using 
	/// the <see cref="MockRepository"/> from the scope. 
	/// </summary>
	public class MoqRegistrationSource : IRegistrationSource
	{
		private readonly MethodInfo _createMethod;
		private readonly MockRepository _repository;

		/// <summary>
		/// Initializes a new instance of the <see cref="MoqRegistrationSource"/> class.
		/// </summary>
		/// <param name="repository">The repository.</param>
		public MoqRegistrationSource(MockRepository repository)
		{
			_repository = repository;
			_createMethod = GetType().GetMethod("CreateUsingRepository", BindingFlags.NonPublic | BindingFlags.Instance).GetGenericMethodDefinition();
		}

		#region IRegistrationSource Members

		/// <summary>
		/// Retrieve registrations for an unregistered service, to be used
		/// by the container.
		/// </summary>
		/// <param name="service">The service that was requested.</param>
		/// <param name="registrationAccessor">A function that will return existing registrations for a service.</param>
		/// <returns>
		/// Registrations providing the service.
		/// </returns>
		public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
		{
			if (service == null) throw new ArgumentNullException("service");

			var typedService = service as TypedService;
			if ((typedService == null) || (!typedService.ServiceType.IsInterface))
				yield break;

			yield return RegistrationBuilder
					  .ForDelegate((context, parameters) =>
					  {
						  MethodInfo typedMethod = _createMethod.MakeGenericMethod(new[] { typedService.ServiceType });
						  var mock = (Mock)typedMethod.Invoke(this, null);
						  return mock.Object;
					  })
					  .As(typedService)
					  .SingleInstance()
					  .CreateRegistration();
		}

		/// <summary>
		/// Gets whether the registrations provided by this source are 1:1 adapters on top
		/// of other components (I.e. like Meta, Func or Owned.)
		/// </summary>
		public bool IsAdapterForIndividualComponents
		{
			get { return false; }
		}

		#endregion

		// ReSharper disable UnusedMember.Local
		/// <summary>
		/// Creates the desired mock using repository logic. This abstraction exists to simplify the process of using
		/// reflection to call a generic method; the repository Create method has several overloads that would cause
		/// the resulting reflection code to be more complex.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		private Mock<T> CreateUsingRepository<T>() where T : class
		{
			return _repository.Create<T>();
		}
		// ReSharper restore UnusedMember.Local
	}
}
